package org.franca.importer.ea;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.franca.core.franca.FArgument;
import org.franca.core.franca.FArrayType;
import org.franca.core.franca.FAttribute;
import org.franca.core.franca.FBroadcast;
import org.franca.core.franca.FEnumerationType;
import org.franca.core.franca.FEnumerator;
import org.franca.core.franca.FField;
import org.franca.core.franca.FInterface;
import org.franca.core.franca.FMapType;
import org.franca.core.franca.FMethod;
import org.franca.core.franca.FModel;
import org.franca.core.franca.FStructType;
import org.franca.core.franca.FType;
import org.franca.core.franca.FTypeDef;
import org.franca.core.franca.FTypeRef;
import org.franca.core.franca.FUnionType;
import org.franca.core.franca.FrancaFactory;
import org.franca.importer.ea.post.PostProcessor;
import org.franca.importer.ea.processor.ImportProcessor;
import org.franca.importer.ea.utils.EAConstants.EParamType;
import org.franca.importer.ea.utils.EAConstants;
import org.franca.importer.ea.utils.EARepositoryAccessor;
import org.franca.importer.ea.utils.FTypeSorter;
import org.sparx.Attribute;
import org.sparx.Element;
import org.sparx.Method;
import org.sparx.Package;
import org.sparx.Parameter;

public class Franca2EA {

	final private ImportProcessor processor;
	
	final private PostProcessor postProcessor;

	private static Logger jlog =  Logger.getLogger(Franca2EA.class.getName());

	public Franca2EA(ImportProcessor processor, PostProcessor postProcessor) {
		this.processor = processor;
		this.postProcessor = postProcessor;
	}

	public Package processRootPackage(Package parent, FModel src) {

		String packageName = src.getName();
		
		if(src.getInterfaces().size() > 0) {
			packageName += "." + src.getInterfaces().get(0).getName();
		}
		
		Package p = processor
				.makePackage(parent, src, packageName);

		// Check if the broadcast interfaces are configured to be own interfaces
		if(EAConstants.BROADCASTINTERFACE_GENERATE.equals("true")) {
			FInterface broadcastInterface = null;
			
			for (FInterface fi : src.getInterfaces()) {
				if(fi.getBroadcasts().size() > 0) {
					broadcastInterface = FrancaFactory.eINSTANCE.createFInterface();
					for(FBroadcast b: fi.getBroadcasts()) {
						FBroadcast copy = EcoreUtil.copy(b);
						broadcastInterface.getBroadcasts().add(copy);
					}
					fi.getBroadcasts().clear();
					broadcastInterface.setName(fi.getName()+EAConstants.BROADCASTINTERFACE_SUFFIX);
					broadcastInterface.setVersion(fi.getVersion());
				}			
			}

			if(broadcastInterface != null) {
				src.getInterfaces().add(broadcastInterface);
			}			
		}
		// ************
		
		for (FInterface fi : src.getInterfaces()) {					
			transformInterface(p, fi);
		}
		
		if(src.getTypeCollections().size() > 0) {
		
			FTypeSorter typeDependencyResolver = new FTypeSorter(src.getTypeCollections().get(0).getTypes());
			typeDependencyResolver.sort();
			
			for (FType ft : typeDependencyResolver.getSorted()) {
				transformType(p, ft);
			}
		}
		
		postProcessor.doConsistencyCheckForPackage(p, src);

		return p;
	}

	private void transformType(Package parent, FType type) {

		if (type instanceof FEnumerationType) {
			transformEnumeration(parent, (FEnumerationType) type);
		} else if (type instanceof FStructType) {
			transformStructure(parent, (FStructType) type);
		} else if (type instanceof FUnionType) {
			transformUnion(parent, (FUnionType) type);
		} else if (type instanceof FTypeDef) {
			transformTypedef(parent, (FTypeDef) type);
		} else if(type instanceof FArrayType) {
			transformArray(parent, (FArrayType) type);
		} else if(type instanceof FMapType) {
			transformMap(parent, (FMapType) type);
		}
				
	}

	private Element transformMap(Package parent, FMapType type) {

		Element map = processor.makeMap(parent, type);
		
		processor.makeMapKey(map, type.getKeyType());
		
		processor.makeMapValue(map, type.getValueType());
		
		return map;		
	}

	private Element transformInterface(Package parent, FInterface src) {

		Element p = processor.makeInterface(parent, src);
		
		for(FAttribute a: src.getAttributes()) {
			transformAttribute(p, a);
		}
		
		for (FMethod m : src.getMethods()) {
			transformMethod(p, m);
		}

		for (FBroadcast b : src.getBroadcasts()) {
			transformMethod(p, b);
		}
		
		for (FType ft : src.getTypes()) {
			transformType(parent, ft);
		}

		p.Refresh();

		return p;
	}

	private Attribute transformAttribute(Element parent, FAttribute src) {
		
		Attribute a = processor.makeAttribute(parent, src);
		
		return a;
	}
	
	private Method transformMethod(Element parent, FMethod src) {

		Method m = processor.makeSimpleMethod(parent, src);
		
		for (FArgument in : src.getInArgs()) {
			transformParameter(m, EParamType.IN, in);
		}

		for (FArgument out : src.getOutArgs()) {
			transformParameter(m, EParamType.OUT, out);
		}

		return m;
	}

	private Method transformMethod(Element parent, FBroadcast src) {

		Method m = processor.makeBroadcastMethod(parent, src);

		for (FArgument out : src.getOutArgs()) {
			transformParameter(m, EParamType.OUT, out);
		}

		return m;

	}

	private Parameter transformParameter(Method parent, EParamType direction,
			FArgument src) {

		Parameter p = processor.makeParameter(parent, direction.getName(), src);

		return p;

	}

	private Element transformTypedef(Package parent, FTypeDef src) {
		
		return processor.makeTypedef(parent, src);		
	}

	private Element transformArray(Package parent, FArrayType src) {
		
		return processor.makeArray(parent, src);		
	}

	private Element transformStructure(Package parent, FStructType src) {

		Element e = processor.makeStructure(parent, src);

		for (FField field : src.getElements()) {
			transformField(e, field);
		}

		postProcessor.doConsistencyCheckForStructure(e, src);
		
		return e;
	}

	private Element transformUnion(Package parent, FUnionType src) {

		Element e = processor.makeUnion(parent, src);

		for (FField field : src.getElements()) {
			transformField(e, field);
		}

		postProcessor.doConsistencyCheckForUnion(e, src);
		
		return e;
	}	
	
	private Attribute transformField(Element parent, FField src) {
		
		if(src.getType().getDerived() != null) {
			Element result = EARepositoryAccessor.INSTANCE.findElement(src.getType().getDerived().getName());
			if(result == null) {
				jlog.log(Level.WARNING, "Unknown element: "+src.getType().getDerived().getName()+" "+
						src.getType().getDerived().eResource().toString());
								
			}
		}
		
		return processor.makeField(parent, src);

	}

	private Element transformEnumeration(Package parent, FEnumerationType src) {

		Element e = processor.makeEnumeration(parent, src);

		for (FEnumerator eLiteral : src.getEnumerators()) {
			transformEnumerator(e, eLiteral);
		}

		postProcessor.doConsistencyCheckForEnumeration(e, src);
		
		return e;
	}

	private Attribute transformEnumerator(Element parent, FEnumerator src) {

		return processor.makeEnumerator(parent, src);

	}


}
